{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "DADUIVuPIqYi"
      },
      "source": [
        "##### Copyright 2019 Google LLC"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "Cu1zPez8Ip1S"
      },
      "outputs": [],
      "source": [
        "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "#     https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "nlIh_TPLI54s"
      },
      "source": [
        "# 自然グラフを用いた文書分類のためのグラフ正則化"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "pL9fF9FWI-Q1"
      },
      "source": [
        "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
        "  <td><a target=\"_blank\" href=\"https://www.tensorflow.org/neural_structured_learning/tutorials/graph_keras_mlp_cora\"><img src=\"https://www.tensorflow.org/images/tf_logo_32px.png\">View on TensorFlow.org</a></td>\n",
        "  <td><a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/docs-l10n/blob/master/site/ja/neural_structured_learning/tutorials/graph_keras_mlp_cora.ipynb\"><img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\"> Google Colab で実行</a></td>\n",
        "  <td><a target=\"_blank\" href=\"https://github.com/tensorflow/docs-l10n/blob/master/site/ja/neural_structured_learning/tutorials/graph_keras_mlp_cora.ipynb\"><img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\">GitHub でソースを表示{</a></td>\n",
        "</table>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "jJlN8oxhNGto"
      },
      "source": [
        "## 概要\n",
        "\n",
        "グラフ正則化は、Neural Graph Learning（[Bui et al.、2018](https://research.google/pubs/pub46568.pdf)）の広範なパラダイムに基づく固有の手法です。その中核となる考え方は、グラフ正則化された対象を持つニューラルネットワークモデルを、ラベル付けされたデータとラベル付けされていないデータの両方を使用してトレーニングすることです。\n",
        "\n",
        "このチュートリアルでは、自然な（有機的な）グラフを形成する文書を分類するためにグラフ正則化を使用することについて見ていきます。\n",
        "\n",
        "Neural Structured Learning（NSL）フレームワークを使用してグラフ正則化モデルを作成する、一般的な方策は以下の通りです。\n",
        "\n",
        "1. 入力グラフとサンプル特徴からトレーニングデータを生成します。グラフのノードはサンプルに対応し、グラフのエッジはサンプルのペア間の類似性に対応します。結果として得られるトレーニングデータには、元のノード特徴に加え、近傍特徴が含まれます。\n",
        "2. `Keras` Sequential API、Functional API、または Subclass API を使用して、基本モデルとしてニューラルネットワークを作成します。\n",
        "3. NSL フレームワークが提供する **`GraphRegularization`** ラッパークラスで基本モデルをラップし、新しいグラフ `Keras` モデルを作成します。この新しいモデルは、トレーニング目的の正則化項にグラフ正則化損失を含みます。\n",
        "4. グラフ `Keras` モデルをトレーニングして評価します。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "nDOFbB34KY1R"
      },
      "source": [
        "## セットアップ\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "hgSLDi0SyBuO"
      },
      "source": [
        "Neural Structured Learning パッケージをインストールします。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "uVnjPmOaQlnH"
      },
      "outputs": [],
      "source": [
        "!pip install --quiet neural-structured-learning"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "0AiNrPJaX_Lb"
      },
      "source": [
        "## 依存関係とインポート"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "5sEamf-wZJkX"
      },
      "outputs": [],
      "source": [
        "import neural_structured_learning as nsl\n",
        "\n",
        "import tensorflow as tf\n",
        "\n",
        "# Resets notebook state\n",
        "tf.keras.backend.clear_session()\n",
        "\n",
        "print(\"Version: \", tf.__version__)\n",
        "print(\"Eager mode: \", tf.executing_eagerly())\n",
        "print(\n",
        "    \"GPU is\",\n",
        "    \"available\" if tf.config.list_physical_devices(\"GPU\") else \"NOT AVAILABLE\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "RtbcGZ_N6Ll9"
      },
      "source": [
        "## Cora データセット\n",
        "\n",
        "[Cora データセット](https://linqs.soe.ucsc.edu/data)はノードが機械学習論文を表現し、エッジが論文のペア間の引用を表現する引用グラフです。このタスクには、各論文を 7 つのカテゴリのいずれかに分類することを目的とする文書分類が含まれています。言い換えると、これは 7 つのクラスを持つマルチクラス分類問題です。\n",
        "\n",
        "### グラフ\n",
        "\n",
        "元のグラフは有向グラフです。しかし、この例の目的のためには無向グラフを考慮します。つまり、論文 A が論文 B を引用している場合、論文 B も論文 A を引用していると考えます。これは必ずしも正しいわけではありませんが、この例においては、引用を類似性のプロキシとみなしており、通常は可換性を持つとみなされます。\n",
        "\n",
        "### 特徴\n",
        "\n",
        "入力された各論文には、次の 2 つの特徴が効果的に含まれています。\n",
        "\n",
        "1. **単語**：論文中のテキストを密でマルチホットな Bag of Words（BoW）表現にしたもの。Cora データセットの語彙には 1433 個のユニークな単語が含まれています。つまり、この特徴の長さは 1433 で、 'i' の位置の値は語彙中の単語 'i' が論文中に存在するかどうかを示す 0 か 1 です。\n",
        "\n",
        "2. **ラベル**: 論文のクラス ID（カテゴリ）を表現する単一の整数。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "p2-FVpVHEyIS"
      },
      "source": [
        "### Cora データセットをダウンロードする"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "2nSZjKqwE6Rn"
      },
      "outputs": [],
      "source": [
        "!wget --quiet -P /tmp https://linqs-data.soe.ucsc.edu/public/lbc/cora.tgz\n",
        "!tar -C /tmp -xvzf /tmp/cora.tgz"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ylYP32_IoqZI"
      },
      "source": [
        "### Cora データセットを NSL 形式に変換する\n",
        "\n",
        "Cora データセットを前処理し、Neural Structured Learning に必要な形式に変換するために、NSL の GitHub リポジトリに含まれている **'preprocess_cora_dataset.py'** スクリプトを実行します。このスクリプトは以下を行います。\n",
        "\n",
        "1. 元のノード特徴とグラフを使用して近傍特徴を生成します。\n",
        "2. `tf.train.Example` インスタンスを含むトレーニングデータとテストデータの分割を生成します。\n",
        "3. 結果として得られたトレーニングデータとテストデータを `TFRecord` 形式で永続化します。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Myz01LVZQ8Uh"
      },
      "outputs": [],
      "source": [
        "!wget https://raw.githubusercontent.com/tensorflow/neural-structured-learning/master/neural_structured_learning/examples/preprocess/cora/preprocess_cora_dataset.py\n",
        "\n",
        "!python preprocess_cora_dataset.py \\\n",
        "--input_cora_content=/tmp/cora/cora.content \\\n",
        "--input_cora_graph=/tmp/cora/cora.cites \\\n",
        "--max_nbrs=5 \\\n",
        "--output_train_data=/tmp/cora/train_merged_examples.tfr \\\n",
        "--output_test_data=/tmp/cora/test_examples.tfr"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "DXoyHIdV5hEe"
      },
      "source": [
        "## グローバル変数\n",
        "\n",
        "トレーニングデータとテストデータへのファイルパスは、上記 **'preprocess_cora_dataset.py'** スクリプトの呼び出しに使用したコマンドラインフラグの値に基づきます。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "kKAmzKIH6I9f"
      },
      "outputs": [],
      "source": [
        "### Experiment dataset\n",
        "TRAIN_DATA_PATH = '/tmp/cora/train_merged_examples.tfr'\n",
        "TEST_DATA_PATH = '/tmp/cora/test_examples.tfr'\n",
        "\n",
        "### Constants used to identify neighbor features in the input.\n",
        "NBR_FEATURE_PREFIX = 'NL_nbr_'\n",
        "NBR_WEIGHT_SUFFIX = '_weight'"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "2gYWAqJqZ76I"
      },
      "source": [
        "## ハイパーパラメータ\n",
        "\n",
        "`HParams` のインスタンスを使用して、トレーニングと評価に使用する様々なハイパーパラメータと定数をインクルードします。それぞれについての簡単な説明を以下に示します。\n",
        "\n",
        "- **num_classes**：7 つの異なるクラスがあります。\n",
        "\n",
        "- **max_seq_length**：これは語彙のサイズであり、入力内のすべてのインスタンスは、密でマルチホットな Bag of Words 表現を持ちます。言い換えれば、ある単語の値が 1 ならば、その単語が入力内に存在することを示し、値が 0 ならば、その単語が入力内に存在しないことを示します。\n",
        "\n",
        "- **distance_type**：これはサンプルをその近傍と正則化する際に使用する距離メトリックです。\n",
        "\n",
        "- **graph_regularization_multiplier**：これは全体の損失関数においてグラフ正則化項の相対的な重みを制御します。\n",
        "\n",
        "- **num_neighbors**：グラフ正則化に使用する近傍の数を指定します。この値は上で `preprocess_cora_dataset.py` の実行時に使用したコマンドライン引数 `max_nbrs` 以下でなければなりません。\n",
        "\n",
        "- **num_fc_units**：ニューラルネットワーク内の完全に接続されたレイヤー数。\n",
        "\n",
        "- **train_epochs**：トレーニングのエポック数。\n",
        "\n",
        "- **batch_size**：トレーニングや評価に使用するバッチサイズ。\n",
        "\n",
        "- **dropout_rate**：完全に接続された各レイヤーの後のドロップアウト率を制御します。\n",
        "\n",
        "- **eval_steps**：評価が完了したと判断するまでに処理を行うバッチ数。`None` 設定にすると、テストセット内の全てのインスタンスを評価します。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "N03EFEkcOBaJ"
      },
      "outputs": [],
      "source": [
        "class HParams(object):\n",
        "  \"\"\"Hyperparameters used for training.\"\"\"\n",
        "  def __init__(self):\n",
        "    ### dataset parameters\n",
        "    self.num_classes = 7\n",
        "    self.max_seq_length = 1433\n",
        "    ### neural graph learning parameters\n",
        "    self.distance_type = nsl.configs.DistanceType.L2\n",
        "    self.graph_regularization_multiplier = 0.1\n",
        "    self.num_neighbors = 1\n",
        "    ### model architecture\n",
        "    self.num_fc_units = [50, 50]\n",
        "    ### training parameters\n",
        "    self.train_epochs = 100\n",
        "    self.batch_size = 128\n",
        "    self.dropout_rate = 0.5\n",
        "    ### eval parameters\n",
        "    self.eval_steps = None  # All instances in the test set are evaluated.\n",
        "\n",
        "HPARAMS = HParams()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "IMp34x0MfMMZ"
      },
      "source": [
        "## トレーニングデータとテストデータを読み込む\n",
        "\n",
        "先にこのノートブックで説明したように、入力のトレーニングデータとテストデータは **'preprocess_cora_dataset.py'** によって作成されています。これらのデータを 2 つの `tf.data.Dataset` オブジェクトに読み込みます。1 つはトレーニング用、もう1 つはテスト用です。\n",
        "\n",
        "モデルの入力レイヤー内では、各サンプルから「単語」と「ラベル」の特徴だけでなく、`hparams.num_neighbors` の値に基づき対応する近傍特徴も抽出します。近傍が `hparams.num_neighbors` よりも少ないインスタンスでは、存在しない近傍特徴にダミー値を割り当てます。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "LCKQVKGee1ST"
      },
      "outputs": [],
      "source": [
        "def make_dataset(file_path, training=False):\n",
        "  \"\"\"Creates a `tf.data.TFRecordDataset`.\n",
        "\n",
        "  Args:\n",
        "    file_path: Name of the file in the `.tfrecord` format containing\n",
        "      `tf.train.Example` objects.\n",
        "    training: Boolean indicating if we are in training mode.\n",
        "\n",
        "  Returns:\n",
        "    An instance of `tf.data.TFRecordDataset` containing the `tf.train.Example`\n",
        "    objects.\n",
        "  \"\"\"\n",
        "\n",
        "  def parse_example(example_proto):\n",
        "    \"\"\"Extracts relevant fields from the `example_proto`.\n",
        "\n",
        "    Args:\n",
        "      example_proto: An instance of `tf.train.Example`.\n",
        "\n",
        "    Returns:\n",
        "      A pair whose first value is a dictionary containing relevant features\n",
        "      and whose second value contains the ground truth label.\n",
        "    \"\"\"\n",
        "    # The 'words' feature is a multi-hot, bag-of-words representation of the\n",
        "    # original raw text. A default value is required for examples that don't\n",
        "    # have the feature.\n",
        "    feature_spec = {\n",
        "        'words':\n",
        "            tf.io.FixedLenFeature([HPARAMS.max_seq_length],\n",
        "                                  tf.int64,\n",
        "                                  default_value=tf.constant(\n",
        "                                      0,\n",
        "                                      dtype=tf.int64,\n",
        "                                      shape=[HPARAMS.max_seq_length])),\n",
        "        'label':\n",
        "            tf.io.FixedLenFeature((), tf.int64, default_value=-1),\n",
        "    }\n",
        "    # We also extract corresponding neighbor features in a similar manner to\n",
        "    # the features above during training.\n",
        "    if training:\n",
        "      for i in range(HPARAMS.num_neighbors):\n",
        "        nbr_feature_key = '{}{}_{}'.format(NBR_FEATURE_PREFIX, i, 'words')\n",
        "        nbr_weight_key = '{}{}{}'.format(NBR_FEATURE_PREFIX, i,\n",
        "                                         NBR_WEIGHT_SUFFIX)\n",
        "        feature_spec[nbr_feature_key] = tf.io.FixedLenFeature(\n",
        "            [HPARAMS.max_seq_length],\n",
        "            tf.int64,\n",
        "            default_value=tf.constant(\n",
        "                0, dtype=tf.int64, shape=[HPARAMS.max_seq_length]))\n",
        "\n",
        "        # We assign a default value of 0.0 for the neighbor weight so that\n",
        "        # graph regularization is done on samples based on their exact number\n",
        "        # of neighbors. In other words, non-existent neighbors are discounted.\n",
        "        feature_spec[nbr_weight_key] = tf.io.FixedLenFeature(\n",
        "            [1], tf.float32, default_value=tf.constant([0.0]))\n",
        "\n",
        "    features = tf.io.parse_single_example(example_proto, feature_spec)\n",
        "\n",
        "    label = features.pop('label')\n",
        "    return features, label\n",
        "\n",
        "  dataset = tf.data.TFRecordDataset([file_path])\n",
        "  if training:\n",
        "    dataset = dataset.shuffle(10000)\n",
        "  dataset = dataset.map(parse_example)\n",
        "  dataset = dataset.batch(HPARAMS.batch_size)\n",
        "  return dataset\n",
        "\n",
        "\n",
        "train_dataset = make_dataset(TRAIN_DATA_PATH, training=True)\n",
        "test_dataset = make_dataset(TEST_DATA_PATH)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "hEWEGhtVzI2p"
      },
      "source": [
        "トレーニングデータセットの中身を覗いてみましょう。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "gx-YFaBoCOcl"
      },
      "outputs": [],
      "source": [
        "for feature_batch, label_batch in train_dataset.take(1):\n",
        "  print('Feature list:', list(feature_batch.keys()))\n",
        "  print('Batch of inputs:', feature_batch['words'])\n",
        "  nbr_feature_key = '{}{}_{}'.format(NBR_FEATURE_PREFIX, 0, 'words')\n",
        "  nbr_weight_key = '{}{}{}'.format(NBR_FEATURE_PREFIX, 0, NBR_WEIGHT_SUFFIX)\n",
        "  print('Batch of neighbor inputs:', feature_batch[nbr_feature_key])\n",
        "  print('Batch of neighbor weights:',\n",
        "        tf.reshape(feature_batch[nbr_weight_key], [-1]))\n",
        "  print('Batch of labels:', label_batch)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "J7B30hRPzOBE"
      },
      "source": [
        "テストデータセットの中身を覗いてみましょう。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "kNJuM9yJiFtj"
      },
      "outputs": [],
      "source": [
        "for feature_batch, label_batch in test_dataset.take(1):\n",
        "  print('Feature list:', list(feature_batch.keys()))\n",
        "  print('Batch of inputs:', feature_batch['words'])\n",
        "  print('Batch of labels:', label_batch)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "OZhsTo8yio8i"
      },
      "source": [
        "## モデルの定義\n",
        "\n",
        "グラフ正則化の使い方を実証するために、まずこの問題の基本モデルを構築します。2 つの隠れレイヤーとその間にドロップアウトを持つ単純なフィードフォワード ニューラルネットワークを使用します。ここでは `tf.Keras` フレームワークでサポートされているすべてのモデルタイプ（Sequential モデル、Functional モデル、Subclass モデル）を使用して基本モデルを作成します。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "z_kBDDfFiuyI"
      },
      "source": [
        "### Sequential 基本モデル"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "pecJmegfWijx"
      },
      "outputs": [],
      "source": [
        "def make_mlp_sequential_model(hparams):\n",
        "  \"\"\"Creates a sequential multi-layer perceptron model.\"\"\"\n",
        "  model = tf.keras.Sequential()\n",
        "  model.add(\n",
        "      tf.keras.layers.InputLayer(\n",
        "          input_shape=(hparams.max_seq_length,), name='words'))\n",
        "  # Input is already one-hot encoded in the integer format. We cast it to\n",
        "  # floating point format here.\n",
        "  model.add(\n",
        "      tf.keras.layers.Lambda(lambda x: tf.keras.backend.cast(x, tf.float32)))\n",
        "  for num_units in hparams.num_fc_units:\n",
        "    model.add(tf.keras.layers.Dense(num_units, activation='relu'))\n",
        "    # For sequential models, by default, Keras ensures that the 'dropout' layer\n",
        "    # is invoked only during training.\n",
        "    model.add(tf.keras.layers.Dropout(hparams.dropout_rate))\n",
        "  model.add(tf.keras.layers.Dense(hparams.num_classes, activation='softmax'))\n",
        "  return model"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "FfZWxqVPiz_f"
      },
      "source": [
        "### Functional 基本モデル"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "TU-YE4NXi7PK"
      },
      "outputs": [],
      "source": [
        "def make_mlp_functional_model(hparams):\n",
        "  \"\"\"Creates a functional API-based multi-layer perceptron model.\"\"\"\n",
        "  inputs = tf.keras.Input(\n",
        "      shape=(hparams.max_seq_length,), dtype='int64', name='words')\n",
        "\n",
        "  # Input is already one-hot encoded in the integer format. We cast it to\n",
        "  # floating point format here.\n",
        "  cur_layer = tf.keras.layers.Lambda(\n",
        "      lambda x: tf.keras.backend.cast(x, tf.float32))(\n",
        "          inputs)\n",
        "\n",
        "  for num_units in hparams.num_fc_units:\n",
        "    cur_layer = tf.keras.layers.Dense(num_units, activation='relu')(cur_layer)\n",
        "    # For functional models, by default, Keras ensures that the 'dropout' layer\n",
        "    # is invoked only during training.\n",
        "    cur_layer = tf.keras.layers.Dropout(hparams.dropout_rate)(cur_layer)\n",
        "\n",
        "  outputs = tf.keras.layers.Dense(\n",
        "      hparams.num_classes, activation='softmax')(\n",
        "          cur_layer)\n",
        "\n",
        "  model = tf.keras.Model(inputs, outputs=outputs)\n",
        "  return model"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "8LmAhITRi8M0"
      },
      "source": [
        "### Subclass 基本モデル"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "4l1aK9b_jAw6"
      },
      "outputs": [],
      "source": [
        "def make_mlp_subclass_model(hparams):\n",
        "  \"\"\"Creates a multi-layer perceptron subclass model in Keras.\"\"\"\n",
        "\n",
        "  class MLP(tf.keras.Model):\n",
        "    \"\"\"Subclass model defining a multi-layer perceptron.\"\"\"\n",
        "\n",
        "    def __init__(self):\n",
        "      super(MLP, self).__init__()\n",
        "      # Input is already one-hot encoded in the integer format. We create a\n",
        "      # layer to cast it to floating point format here.\n",
        "      self.cast_to_float_layer = tf.keras.layers.Lambda(\n",
        "          lambda x: tf.keras.backend.cast(x, tf.float32))\n",
        "      self.dense_layers = [\n",
        "          tf.keras.layers.Dense(num_units, activation='relu')\n",
        "          for num_units in hparams.num_fc_units\n",
        "      ]\n",
        "      self.dropout_layer = tf.keras.layers.Dropout(hparams.dropout_rate)\n",
        "      self.output_layer = tf.keras.layers.Dense(\n",
        "          hparams.num_classes, activation='softmax')\n",
        "\n",
        "    def call(self, inputs, training=False):\n",
        "      cur_layer = self.cast_to_float_layer(inputs['words'])\n",
        "      for dense_layer in self.dense_layers:\n",
        "        cur_layer = dense_layer(cur_layer)\n",
        "        cur_layer = self.dropout_layer(cur_layer, training=training)\n",
        "\n",
        "      outputs = self.output_layer(cur_layer)\n",
        "\n",
        "      return outputs\n",
        "\n",
        "  return MLP()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "BbGpIbscjIAo"
      },
      "source": [
        "## 基本モデルを作成する"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "fzMBxiMGjCO4"
      },
      "outputs": [],
      "source": [
        "# Create a base MLP model using the functional API.\n",
        "# Alternatively, you can also create a sequential or subclass base model using\n",
        "# the make_mlp_sequential_model() or make_mlp_subclass_model() functions\n",
        "# respectively, defined above. Note that if a subclass model is used, its\n",
        "# summary cannot be generated until it is built.\n",
        "base_model_tag, base_model = 'FUNCTIONAL', make_mlp_functional_model(HPARAMS)\n",
        "base_model.summary()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "T1uEboimjiW7"
      },
      "source": [
        "## 基本 MLP モデルをトレーニングする"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "JALapM4PoCvi"
      },
      "outputs": [],
      "source": [
        "# Compile and train the base MLP model\n",
        "base_model.compile(\n",
        "    optimizer='adam',\n",
        "    loss='sparse_categorical_crossentropy',\n",
        "    metrics=['accuracy'])\n",
        "base_model.fit(train_dataset, epochs=HPARAMS.train_epochs, verbose=1)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "gPRioqydQD_8"
      },
      "source": [
        "## 基本 MLP モデルを評価する"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "2NcsJVt6FSmZ"
      },
      "outputs": [],
      "source": [
        "# Helper function to print evaluation metrics.\n",
        "def print_metrics(model_desc, eval_metrics):\n",
        "  \"\"\"Prints evaluation metrics.\n",
        "\n",
        "  Args:\n",
        "    model_desc: A description of the model.\n",
        "    eval_metrics: A dictionary mapping metric names to corresponding values. It\n",
        "      must contain the loss and accuracy metrics.\n",
        "  \"\"\"\n",
        "  print('\\n')\n",
        "  print('Eval accuracy for ', model_desc, ': ', eval_metrics['accuracy'])\n",
        "  print('Eval loss for ', model_desc, ': ', eval_metrics['loss'])\n",
        "  if 'graph_loss' in eval_metrics:\n",
        "    print('Eval graph loss for ', model_desc, ': ', eval_metrics['graph_loss'])"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "-myfttwIQAwc"
      },
      "outputs": [],
      "source": [
        "eval_results = dict(\n",
        "    zip(base_model.metrics_names,\n",
        "        base_model.evaluate(test_dataset, steps=HPARAMS.eval_steps)))\n",
        "print_metrics('Base MLP model', eval_results)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "bGwSzS9Spaiu"
      },
      "source": [
        "## MLP モデルをグラフ正則化でトレーニングする\n",
        "\n",
        "既存の `tf.Keras.Model` の損失項にグラフ正則化を組み込む場合に必要なのは、数行のコードのみです。基本モデルをラップして、損失にグラフ正則化を含んだ新しい `tf.Keras` サブクラスモデルを作成します。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "vuIGN8KQH0jR"
      },
      "source": [
        "グラフ正則化の増分効果を評価するために、基本モデルの新しいインスタンスを作成します。これは、`base_model` は既に数回のイテレーションでトレーニングされているため、このトレーニング済みモデルを再利用してグラフ正則化モデルを作成しても `base_model` の公平な比較をすることができないからです。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "6fvLei9dLCH0"
      },
      "outputs": [],
      "source": [
        "# Build a new base MLP model.\n",
        "base_reg_model_tag, base_reg_model = 'FUNCTIONAL', make_mlp_functional_model(\n",
        "    HPARAMS)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "HT3mpC8Lo1UZ"
      },
      "outputs": [],
      "source": [
        "# Wrap the base MLP model with graph regularization.\n",
        "graph_reg_config = nsl.configs.make_graph_reg_config(\n",
        "    max_neighbors=HPARAMS.num_neighbors,\n",
        "    multiplier=HPARAMS.graph_regularization_multiplier,\n",
        "    distance_type=HPARAMS.distance_type,\n",
        "    sum_over_axis=-1)\n",
        "graph_reg_model = nsl.keras.GraphRegularization(base_reg_model,\n",
        "                                                graph_reg_config)\n",
        "graph_reg_model.compile(\n",
        "    optimizer='adam',\n",
        "    loss='sparse_categorical_crossentropy',\n",
        "    metrics=['accuracy'])\n",
        "graph_reg_model.fit(train_dataset, epochs=HPARAMS.train_epochs, verbose=1)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "6409ylRVQS7q"
      },
      "source": [
        "## MLP モデルをグラフ正則化で評価する"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "1TsOE1bAQTqD"
      },
      "outputs": [],
      "source": [
        "eval_results = dict(\n",
        "    zip(graph_reg_model.metrics_names,\n",
        "        graph_reg_model.evaluate(test_dataset, steps=HPARAMS.eval_steps)))\n",
        "print_metrics('MLP + graph regularization', eval_results)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Adc-r84EOSQi"
      },
      "source": [
        "グラフ正則化モデルの精度は、基本モデル（`base_model`）の精度に比べて 2-3% 程度高くなります。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "OEXQHFNvJQJe"
      },
      "source": [
        "## 結論\n",
        "\n",
        "Neural Structured Learning（NSL）フレームワークを用いて、自然な引用グラフ上の文書（Cora）分類のためのグラフ正則化の使用について実証しました。[上級者向けチュートリアル](graph_keras_lstm_imdb.ipynb)では、グラフ正則化を使用してニューラルネットワークをトレーニングする前に、サンプル埋め込みに基づいたグラフを合成します。このアプローチは、入力に明示的なグラフが含まれない場合に有用です。\n",
        "\n",
        "ユーザーの方々には、グラフ正則化のさまざまなニューラルアーキテクチャを試してみると共に、監視の量を加減してさらに深く実験を行うことを推奨しています。"
      ]
    }
  ],
  "metadata": {
    "accelerator": "GPU",
    "colab": {
      "collapsed_sections": [],
      "name": "graph_keras_mlp_cora.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
